import Box2D.b2 as b2
import math
import pyglet

from camera import world_to_pixels, pixels_to_world
from constants import APPLY_GRAVITY
from constants import MAX_ANGLE_AGAINST_GROUND_NORMAL_FOR_JUMP as MAX_ANGLE
from constants import STRENGTH_OF_GRAVITY as G
from constants import STRENGTH_OF_PLAYER_FRICTION as FRICTION
from constants import STRENGTH_OF_JUMP as JUMP
from constants import STRENGTH_OF_MOVEMENT as MOVE
from debug import msg
import entity
import goal
import portalable
import resources


class PlayerObject(entity.Dynamic):
    def __init__(self, mode, img, start_configuration,
                 front_is_left=False,
                 portalable=None):
        self.anchor = (0.5, 0)
        self.fixed_rotation = True
        self.e_type = "player"
        self.friction = FRICTION
        self.density = 0.7

        self.moving = False

        super(PlayerObject, self).__init__(mode, img,
                                           start_configuration,
                                           front_is_left=front_is_left,
                                           portalable=portalable)

        # Sets body to "bullet". Somewhat expensive, but stops weird
        # behaviour when colliding with dynamic entities.
        self.body.bullet = True

        # Add a pendulum to indicate gravity-direction. Consists of a
        # body fixed to the box with a joint and a sprite.

        # Load image.
        img = resources.image("data/img/player_pend.png", (0.5,1))

        # Create body, positioned on the anchorposition in the player
        # sprite.
        (pos, vel, rot) = start_configuration

        self.joint_pos = (b2.vec2(pos) -
                          (pixels_to_world(self.sprite.height) -
                           pixels_to_world(self.sprite.width/2.0)) *
                          self.gravity_dir)

        self.pendulum_body = mode.world.CreateDynamicBody(
            position=self.joint_pos,
            angle=rot)

        self.pendulum_fixture = self.pendulum_body.CreatePolygonFixture(
            box=(pixels_to_world(img.width/2.0),
                 pixels_to_world(1.5),
                 (0, -pixels_to_world(11)),
                 0),
            density=0.01,
            userData={"self":self})

        self.pendulum_fixture.sensor = True

        # Create a joint between the pendulum and the box.
        self.pendulum_joint = mode.world.CreateRevoluteJoint(
            bodyA = self.body,
            bodyB = self.pendulum_body,
            anchor = self.pendulum_body.position,
            enableMotor = True,
            motorSpeed = 0.0,
            maxMotorTorque = 0.001)

        # Add image
        self.pendulum_sprite = pyglet.sprite.Sprite(
            img, batch=self.mode.batch_below_dynamic)

        self.update_sprite()

    def move(self, direction, force):
        """
        Moves the player-object according to user commands.
        """
        if self.is_grounded()[0]:
            f = force
        else:
            f = force * 0.65

        self.body.ApplyForce(direction * f * self.face_dir,
                             self.body.worldCenter)

    def stop(self, force):
        """
        Applies a stopping force to the player. Called when either
        none or both movement-keys are pressed.

        force -- The force to be applied. Given from player.
        """
        vx = self.body.linearVelocity.dot(self.face_dir)
        if abs(vx) < 1e-6:
            # Nullifies the locally horizontal velocity if it is
            # close to zero.
            self.body.ApplyLinearImpulse(-vx*self.face_dir,
                                          self.body.worldCenter)
        else:
            # Applies a force to slow down the players locally
            # horizontal movement.
            self.body.ApplyForce(math.copysign(force, -vx)*self.face_dir,
                                 self.body.worldCenter)

    def update_sprite(self):
        """
        Extends function from entity.Dynamic to update
        pendulum_sprite.
        """
        super(PlayerObject, self).update_sprite()

        try:
            self.pendulum_sprite.x = world_to_pixels(self.joint_pos[0])
            self.pendulum_sprite.y = world_to_pixels(self.joint_pos[1])

            # Pyglet sprites rotate clockwise
            self.pendulum_sprite.rotation = (-self.pendulum_body.angle) * (180 / math.pi)
        except AttributeError:
            pass


    def update(self, dt):
        """
        Extends function from entity.Dynamic.
        """
        self.joint_pos = (self.body.position -
                          (pixels_to_world(self.sprite.height) -
                           pixels_to_world(self.sprite.width/2.0)) *
                          self.gravity_dir)

        super(PlayerObject, self).update(dt)

        if APPLY_GRAVITY:
            try:
                self.pendulum_body.ApplyForce(G * self.pendulum_body.mass * self.gravity_dir,
                                     self.pendulum_body.worldCenter)
            except AttributeError:
                pass

        self.pendulum_joint.motorSpeed = self.body.angularVelocity

class Player(portalable.Portalable):
    def __init__(self, mode, start_configurations, flipped=False):
        self.image = resources.image("player.png", (0.5, 0))
        self.image_flipped = resources.image("player.png", (0.5, 0), flip_x=True)

        self.flipped = flipped # Fix this, you ugly person

        super(Player, self).__init__(mode, start_configurations,
                                     e_type="player", fixed_rotation=True)

        self.keys = {"left": False,
                     "right": False,
                     "up": False,
                     }
        self.jump_allowed = False
        self.just_jumped = False

    def add_instance(self, start_configuration,
                     front_is_left=False):
        if self.flipped:
            o = PlayerObject(self.mode, self.image_flipped,
                             start_configuration,
                             front_is_left=front_is_left,
                             portalable=self)
        else:
            o = PlayerObject(self.mode, self.image,
                             start_configuration,
                             front_is_left=front_is_left,
                             portalable=self)
        self.objects.append(o)
        return o

    def on_key_press(self, key, mods):
        """
        Sets flags to reflect recently pressed keys.

        Arguments:
        key -- recently pressed key
        mods -- applied moderators
        """
        if key in (pyglet.window.key.LEFT, pyglet.window.key.D):
            self.keys["left"] = True
        if key in (pyglet.window.key.RIGHT, pyglet.window.key.A):
            self.keys["right"] = True
        if key in (pyglet.window.key.UP, pyglet.window.key.W, pyglet.window.key.SPACE):
            self.keys["up"] = True
        if key == pyglet.window.key.O:
            self.rotate()
        if key in (pyglet.window.key.DOWN, pyglet.window.key.S):
            self.interact()

    def on_key_release(self, key, mods):
        """
        Sets flags to reflect recently released keys.

        Arguments:
        key -- recently released key
        mods -- applied moderators
        """
        if key in (pyglet.window.key.LEFT, pyglet.window.key.D):
            self.keys["left"] = False
        if key in (pyglet.window.key.RIGHT, pyglet.window.key.A):
            self.keys["right"] = False
        if key in (pyglet.window.key.UP, pyglet.window.key.W, pyglet.window.key.SPACE):
            self.keys["up"] = False

    def interact(self):
        """
        Called when player presses interact button.
        Interacts with all interactable objects within contact.
        """
        for o in self.objects:
            for c in o.body.contacts:
                if c.contact.touching:
                    for f in [c.contact.fixtureA, c.contact.fixtureB]:
                        try:
                            if isinstance(f.userData["self"], goal.Goal):
                                if abs(f.userData["self"].rotation - o.body.angle)%math.pi < 1e-2:
                                    f.userData["self"].interact()
                            else:
                                f.userData["self"].interact()
                        except AttributeError:
                            pass

    def jump(self):
        """
        If a reasonable time has passed since last jump, we check if
        any PlayerObjects are grounded. For every grounded object we
        check if what it stands on is a dynamic body, if so we apply a
        reactive force in the opposite direction on the dynamic
        object.

        If any PlayerObject is grounded we jump, i.e. applies a linear
        impulse "upwards".

        Then a clock is set to decide when a new jump will be allowed.
        """
        if not self.just_jumped:
            will_jump = False
            for i in self.objects:
                (grounded, dynamics_and_points) = i.is_grounded()
                if grounded:
                    for (dynamic, points) in dynamics_and_points:
                        dynamic.body.ApplyLinearImpulse(
                            i.gravity_dir * JUMP, dynamic.body.worldCenter)
                    will_jump = True
            if will_jump:
                sound = pyglet.resource.media('clock.mp3', streaming=False)
                sound.play()
                for o in self.objects:
                    o.body.ApplyLinearImpulse(
                        -o.gravity_dir * JUMP, o.body.worldCenter)
                    self.just_jumped = True
                    pyglet.clock.schedule_once(self.allow_jump, 0.2)

    def allow_jump(self, dt):
        """
        Called from pyglet clock a certain intervall after every
        jump. To prevent "doublejumping".
        """
        self.just_jumped = False


    def rotate(self):
        """
        Rotates the object and updates the gravity_dir and move_dir
        vectors.
        """
        for o in self.objects:
            o.body.angle += math.pi / 2
            o.gravity_dir = b2.vec2(math.sin(o.body.angle),
                                    -math.cos(o.body.angle))
            o.face_dir = b2.vec2(math.sin(math.pi / 2 - o.body.angle),
                                 math.cos(math.pi / 2 - o.body.angle))

    def update(self, dt):
        self.input_update()
        super(Player, self).update(dt)


    def input_update(self):
        """
        Reads which keys are pressed, and acts accordingly.
        """
        if self.keys["left"] and not self.keys["right"]:
            for o in self.objects:
                o.move(-1, MOVE)
                if o.face_dir.cross(o.gravity_dir) > 0:
                    o.sprite.image = self.image
                else:
                    o.sprite.image = self.image_flipped

        elif self.keys["right"] and not self.keys["left"]:
            for o in self.objects:
                o.move(1, MOVE)
                if o.face_dir.cross(o.gravity_dir) < 0:
                    o.sprite.image = self.image
                else:
                    o.sprite.image = self.image_flipped

        else:
            # If player is grounded it should brake when no
            # directional buttons are pressed.
            grounded = False
            for o in self.objects:
                if o.is_grounded()[0]:
                    grounded = True
            if grounded:
                for o in self.objects:
                    o.stop(MOVE/2)

        if self.keys["up"]:
            self.jump()
